//
//  Card.h
//  PrefCore
//
//  Created by Sergey on 1/8/14.
//  Copyright (c) 2014 Play-on-smart. All rights reserved.
//

#import <Foundation/Foundation.h>

/// Represent a card suit.
typedef enum {
    kSuitSpades
,   kSuitClubs
,   kSuitDiamonds
,   kSuitHearts
,   kSuitNone
} PSCardSuit;

/// Represents the length of the suits array.
#define PS_CARD_SUIT_RISING_LEN 4U

/// Represents the hash of a card's rank.
typedef uint8_t PSCardRankHashType;

/// Represents a collection of one or more cards in the same suit
typedef PSCardRankHashType PSCardRankSet;

/// Represents the index of a card.
typedef uint8_t PSCardIndex;

/// Represents the 32-bit hash of a card.
typedef uint32_t PSCardHash;

/// Contains the suits of cards in ascending order.
extern PSCardSuit PSCardSuitRising[PS_CARD_SUIT_RISING_LEN];

/// Obtains the next higher suit of card.
/// @param suit The suit for which to obtain the next higher suit.
extern PSCardSuit PSCardSuitNext(PSCardSuit suit);

/// Obtains the next lower suit of card.
/// @param suit The suit for which to obtain the next lower suit.
extern PSCardSuit PSCardSuitPrevious(PSCardSuit suit);

/// Obtains the position of a given suit in a larger hash number.
extern PSCardIndex PSCardSuitPower(PSCardSuit suit);

/// Obtains a string representing the description of the suit.
extern NSString* PSCardSuitDescription(PSCardSuit suit);

/// Obtains a short description of the card suit.
extern NSString* PSCardSuitShortName(PSCardSuit suit);

/// Represents a card rank.
typedef enum {
    kRankNull = -1
,   kRankSeven = 0
,   kRankEight
,   kRankNine
,   kRankTen
,   kRankJack
,   kRankQueen
,   kRankKing
,   kRankAce
} PSCardRank;

/// The number of cards in a suit.
#define PS_CARD_RANK_COUNT ((PSCardIndex)8)

/// An array with cards' ranks arranged in ascending order of power.
extern PSCardRank PSCardRankRising[PS_CARD_RANK_COUNT];

/// An array with cards' ranks arranged in descending order of power.
extern PSCardRank PSCardRankFalling[PS_CARD_RANK_COUNT];

/// Constructs a hash for this card's rank.
/// This is done with a macro to stay a compile-time const expression.
#define PSCardRankHash(rank) ((PSCardRankHashType)(1U << (rank)))

/// Obtains a string representing the description of the card rank.
extern NSString *PSCardRankDescription(PSCardRank rank);

/* Card Set Functions */

/// Obtains the number of cards in a set of cards.
/// @param set the hash of the set of cards.
inline PSCardIndex PSCardSetSize(PSCardRankSet set) {
    return __builtin_popcount(set);
}

/// Determine if a card set represented by a hash contains a given card,
/// which is represented by its rank.
/// @param set The hash representing a set of cards.
/// @param rank The rank of the card to check.
inline BOOL PSCardSetContainsRank(PSCardRankSet set, PSCardRank rank) {
    return (set & PSCardRankHash(rank)) != 0;
}

/// Determine if a card set represented by a hash contains a given card,
/// which is represented by the hash of its rank.
/// @param set The hash representing a set of cards.
/// @param card The hash of the card to check.
inline BOOL PSCardSetContainsCard(PSCardRankSet set, PSCardRankHashType card) {
    return (set & card) != 0;
}

/// Combines two card rank sets
/// @param one The first set of cards.
/// @param two The second set of cards.
inline PSCardRankSet PSCardSetCombine(PSCardRankSet one, PSCardRankSet two) {
    return one | two;
}

/// Removes a card from a card set.
/// @param set The hash representing a set of cards.
/// @param rank The rank of the card to remove.
inline PSCardRankSet PSCardSetRemoveCard(PSCardRankSet set, PSCardRankHashType card) {
    return set & ~card;
}

/// Removes a card represented by the hash of its rank from a card set.
/// @param set The hash representing a set of cards.
/// @param rank The rank of the card to remove.
inline PSCardRankSet PSCardSetRemoveRank(PSCardRankSet set, PSCardRank rank) {
    return set & ~PSCardRankHash(rank);
}

/// Finds the highest rank of cards within a given set.
/// @param hash The hash representing a set of cards.
extern PSCardRank PSCardSetHighestRank(PSCardRankSet set);

/// Finds the lowest rank of cards within a given set.
/// @param set The hash representing a set of cards.
extern PSCardRank PSCardSetLowestRank(PSCardRankHashType set);

/// Finds the next higher rank not present in a given set of cards.
/// @param set The hash representing a set of cards.
/// @param rank The rank of the card to check.
extern PSCardRank PSCardSetNextHigherRank(PSCardRankSet set, PSCardRank rank);

/// Finds the next lower rank not present in a given set of cards.
/// @param set The hash representing a set of cards.
/// @param rank The rank of the card to check.
extern PSCardRank PSCardSetNextLowerRank(PSCardRankSet set, PSCardRank rank);

/// Adds a description of cards in this set to the output buffer.
/// @param buf The buffer to which the descriptions need to be added.
/// @param set The hash representing a set of cards.
extern void PSCardSetDescription(NSMutableString *buf, PSCardRankSet set);

/// Represents hash masks used in a card set.
enum {
    kMaskAce = PSCardRankHash(kRankAce)
,   kMaskKing = PSCardRankHash(kRankKing)
,   kMaskQueen = PSCardRankHash(kRankQueen)
,   kMaskJack = PSCardRankHash(kRankJack)
,   kMaskTen = PSCardRankHash(kRankTen)
,   kMaskNine = PSCardRankHash(kRankNine)
,   kMaskEight = PSCardRankHash(kRankEight)
,   kMaskSeven = PSCardRankHash(kRankSeven)
    // card combinations
,   kMaskSevenOrEight = kMaskSeven | kMaskEight
};

/// Determines if a specific index is a valid index of a card.
inline BOOL PSCardIsValidIndex(PSCardIndex compressed) {
    return compressed > 0 && compressed < 33;
}

/// Represents a single playing card.
@interface PSCard : NSObject

/// Obtains a card with the specified rank in the given suit.
/// @param suit The suit of the card.
/// @param rank The rank of the card.
+(instancetype) cardWithSuit:(PSCardSuit) suit andRank:(PSCardRank) rank;

/// Obtaons the card at the specific "compressed" index.
/// @param index The index of the card to be returned.
+(instancetype) cardWithIndex:(PSCardIndex)index;

/// Obtains the card with the specified value of 32-bit hash.
/// @param hash The hash of the card to be returned.
+(instancetype) cardWithHash:(PSCardHash)hash;

/// Obtains the suit of the card with the specified "compressed" index.
/// @param index The index of the card for which the suit is to be retrieved.
+(PSCardSuit) suitOfCardWithIndex:(PSCardIndex)index;

/// Obtains the rank of the card with the specified "compressed" index.
/// @param index The index of the card for which the rank is to be retrieved.
+(PSCardRank) rankOfCardWithIndex:(PSCardIndex)index;

/// The suit of this card.
@property (nonatomic, readonly) PSCardSuit suit;

/// The rank of this card.
@property (nonatomic, readonly) PSCardRank rank;

/// A 32-bit hash of this card.
@property (nonatomic, readonly) PSCardHash hash32;

/// The compressed index of this card.
@property (nonatomic, readonly) PSCardIndex compressed;

@end
